package online.shuita.gitee.mojo.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import online.shuita.gitee.mojo.annotation.IntfDomain;
import online.shuita.gitee.mojo.annotation.ShotName;
import online.shuita.gitee.mojo.doclet.ClassDoc;
import online.shuita.gitee.mojo.doclet.Doclet;
import online.shuita.gitee.mojo.doclet.MethodEntry;
import online.shuita.gitee.mojo.doclet.ParameterEntry;
import online.shuita.gitee.mojo.model.*;
import online.shuita.gitee.mojo.utils.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.TypeUtils;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

@Slf4j
public class IntfService {

    private static Doclet doclet = null;
    private static List<String> excludeField = Arrays.asList(
            "class"
    );
    @Setter
    private String sourceBasePath;
    @Setter
    private List<String> compileClasspathElements;
    @Setter
    private ClassLoader loader;

    private Map<String, ClassDoc> docMap = Maps.newConcurrentMap();

    private Map<String, List<PojoModel>> pojoMap = Maps.newConcurrentMap();

    public AnalyResult doAnalyse(List<Class> intfCls, ClassLoader loader) {
        log.info("start doAnalyse. class array size =" + intfCls.size());
        if (null == loader) {
            return null;
        }
        this.loader = loader;
        if (null == doclet) {
            doclet = new Doclet(compileClasspathElements, loader);
        }
        if (null != intfCls) {
            try {
                List<IntfModel> mdlList = Lists.newArrayList();
                for (Class cls : intfCls) {
                    IntfModel intfModel = doAnalyseClass(cls);
                    log.info("end doAnalyse. class=" + cls.getName());
                    mdlList.add(intfModel);
                }

                return AnalyResult.builder()
                        .intfModelList(mdlList)
                        .pojoModelMap(pojoMap).build();
            } catch (Throwable e) {
                log.error("失败", e);
            }
        }
        return null;
    }

    public AnalyResult doAnalyse(Class intfCls, ClassLoader loader) {
        log.info("start doAnalyse. class=" + intfCls.getName());
        if (null == loader) {
            return null;
        }
        this.loader = loader;
        if (null == doclet) {
            doclet = new Doclet(compileClasspathElements, loader);
        }
        if (null != intfCls) {
            try {
                IntfModel intfModel = doAnalyseClass(intfCls);

                log.info("end doAnalyse. class=" + intfCls.getName());
                return AnalyResult.builder()
                        .intfModelList(Lists.newArrayList(intfModel))
                        .pojoModelMap(pojoMap).build();

            } catch (Throwable e) {
                log.error("失败", e);
            }
        }
        return null;
    }

    /**
     * 解析接口类
     *
     * @param intfCls
     * @return
     */
    private IntfModel doAnalyseClass(Class intfCls) {
        IntfModel intfModel = IntfModel.builder()
                .intfName(intfCls.getName())
                .pkgName(intfCls.getPackage().getName())
                .build();
        //接口简称
        ShotName shotName = (ShotName) intfCls.getAnnotation(ShotName.class);
        if (shotName != null) {
            intfModel.setIntfShortName(shotName.value());
        } else {
            String[] ss = intfModel.getIntfName().split("[.]");
            intfModel.setIntfShortName(ss[ss.length - 1]);
        }
        //业务域
        IntfDomain domain = (IntfDomain) intfCls.getAnnotation(IntfDomain.class);
        if (domain != null) {
            intfModel.setDomain(domain.value());
        } else {
            intfModel.setDomain("");
        }

        //类注释
        ClassDoc doc = getDoc(intfCls.getName());
        if (null != doc) {
            intfModel.setIntfDesc(doc.getModelCommentText());
        }

        //找到所有的api方法
        Method[] methods = intfCls.getMethods();
        intfModel.setMethodModelList(Arrays.stream(methods).map(
                method -> doAnalyseMethod(method, intfCls)
        ).collect(Collectors.toList()));
        return intfModel;
    }

    /**
     * 解析接口函数
     *
     * @param method
     */
    private MethodModel doAnalyseMethod(Method method, Class intfCls) {
        log.info("start analyse method={}", method.toString());
        MethodModel methodModel = MethodModel.builder()
                .methodName(method.toString())
                .methodType(method.toString())
                .build();
        //方法简称
        ShotName shotName = (ShotName) method.getAnnotation(ShotName.class);
        if (shotName != null) {
            methodModel.setMethodShortName(shotName.value());
        } else {
            methodModel.setMethodShortName(method.getName());
        }

        //识别是否废弃接口
        Deprecated deprecated = (Deprecated) method.getAnnotation(Deprecated.class);
        methodModel.setDeprecated(deprecated != null);

        //方法注释
        ClassDoc doc = getDoc(intfCls.getName());
        if (null != doc) {
            Map<String, MethodEntry> mMap = doc.getMethodEntryMap();
            String clsName = method.getDeclaringClass().getName();
            String name = method.getName();
            List<String> ptypelist = Arrays.stream(method.getParameterTypes())
                    .map(item -> item.getName())
                    .collect(Collectors.toList());
            String key = String.format("%s.%s(%s)", clsName, name, String.join(", ", ptypelist));
            MethodEntry entry = mMap.get(key);
            if (null != entry) {
                methodModel.setMethodDesc(entry.getMExplain());

                //方法入参
                Parameter[] parameters = method.getParameters();
                List<ParameterEntry> plist = entry.getParameterList();
                List<ParameterModel> parameterModelList = Lists.newArrayList();
                int idx = 0;
                for (Parameter p : parameters) {
                    parameterModelList.add(doAnalyseParameter(plist.get(idx++), p, intfCls));
                }
                methodModel.setParameterModelList(parameterModelList);
            } else {
                methodModel.setParameterModelList(Lists.newArrayList());
            }
        }

        //方法返回值
        methodModel.setReturnType(method.getReturnType().getName());
        methodModel.setReturnGenericType(method.getGenericReturnType().getTypeName());
        methodModel.setReturnTypeShotName(getShotType(method.getGenericReturnType()));

        methodModel.setMethodName(String.format("%s %s.%s(%s)",
                methodModel.getReturnTypeShotName(),
                getShotName(method.getDeclaringClass().getName()),
                method.getName(),
                String.join(", ", methodModel.getParameterModelList().stream().map(item -> item.getTypeShotName()).collect(Collectors.toList()))
        ));
        methodModel.setMethodShortName(String.format("%s %s(%s)",
                methodModel.getReturnTypeShotName(),
                method.getName(),
                String.join(", ", methodModel.getParameterModelList().stream().map(item -> item.getTypeShotName()).collect(Collectors.toList()))
        ));

        appendPojo(methodModel.getReturnType(), method.getGenericReturnType());

        return methodModel;
    }

    /**
     * 分析方法入参
     *
     * @param parameter
     * @param intfCls
     * @return
     */
    private ParameterModel doAnalyseParameter(ParameterEntry parameterDoc, Parameter parameter, Class intfCls) {
        ParameterModel model = ParameterModel.builder().parameterName(parameterDoc.getParameterName())
                .type(parameter.getType().getName())
                .genericType(parameter.getParameterizedType().getTypeName())
                .typeShotName(getShotType(parameter.getParameterizedType()))
                .desc(parameterDoc.getParameterDesc())
                .build();

        appendPojo(model.getType(), parameter.getParameterizedType());
        return model;
    }

    private ClassDoc getDoc(String className) {
        String sourceFileName = sourceBasePath + Matcher.quoteReplacement(File.separator) + className.replaceAll("[.]", Matcher.quoteReplacement(File.separator)) + ".java";
        if (docMap.containsKey(sourceFileName)) {
            return docMap.get(sourceFileName);
        } else {
            ClassDoc doc = doclet.exec(sourceFileName);
            docMap.putIfAbsent(sourceFileName, doc);
            return doc;
        }
    }

    private String getClassNameType(Type type) {
        if (type instanceof ParameterizedType) {
            return ((ParameterizedType) type).getRawType().getTypeName();
        } else {
            return type.getTypeName();
        }
    }

    private String getShotType(Type type) {
        log.info("{} getShotType:{}", type.getTypeName(), type.getClass().getName());
        if (type instanceof ParameterizedType) {
            return String.format("%s<%s>", getShotName(type.getTypeName()),
                    Arrays.asList(((ParameterizedType) type).getActualTypeArguments())
                            .stream()
                            .map(item -> getShotType(item))
                            .collect(Collectors.joining(", ")));
        }
        return String.format("%s", getShotName(type.getTypeName()));
    }

    private String getShotName(String name) {
        String[] ss = name.split("[.]");
        int idx = 0;
        for (String s : ss) {
            if (s.contains("<")) {
                return s.substring(0, s.indexOf("<"));
            } else if (++idx == ss.length) {
                return s;
            }
        }
        return "";
    }

    private List<PojoModel> appendPojo(String name, Type genericType) {
        log.info("解析类：{}", genericType.getTypeName());
        if (StringUtils.isBlank(name)
                || isBaseType(name)
                || isEnumType(name)) {
            return Lists.newArrayList();
        } else if (pojoMap.containsKey(genericType.getTypeName())) {
            return pojoMap.get(genericType.getTypeName());
        } else if (isCollectionType(name)) {
            //防止死循环，先把结果放入map
            pojoMap.putIfAbsent(genericType.getTypeName(), Lists.newArrayList());

            //如果是集合类，不解析当前类，直接解析泛型的类
            List<PojoModel> models = Lists.newArrayList();
            if (genericType instanceof ParameterizedType) {
                Type[] types = ((ParameterizedType) genericType).getActualTypeArguments();
                for (Type t : types) {
                    log.info("解析集合类的泛型类型：{}", t.getTypeName());
                    models.addAll(appendPojo(getClassNameType(t), t));
                }
            }
            if (models.size() > 0) {
                pojoMap.get(genericType.getTypeName()).addAll(models.stream().distinct().collect(Collectors.toList()));
            } else {
                pojoMap.remove(genericType.getTypeName());
            }
            return models;
        } else if (!pojoMap.containsKey(genericType.getTypeName())) {
            //防止死循环，先把结果放入map
            pojoMap.putIfAbsent(genericType.getTypeName(), Lists.newArrayList());

            List<PojoModel> models = Lists.newArrayList();
            try {
                PojoModel pojo = PojoModel.builder()
                        .pojoName(name)
                        .type(name)
                        .genericType(genericType.getTypeName())
                        .shortType(getShotType(genericType))
                        .build();
                Class target = loader.loadClass(name);

                log.info("=======================");
                log.info(target.toGenericString());

                Map<String, Type> generateParamTypeMap = Maps.newHashMap();
                if (genericType instanceof ParameterizedType) {
                    Type[] arr1 = ((ParameterizedType) genericType).getActualTypeArguments();
                    Type[] arr2 = target.getTypeParameters();
                    log.info(((ParameterizedType) genericType).getActualTypeArguments().length + "");
                    for (int idx = 0; idx < arr1.length; idx++) {
                        Type t1 = arr1[idx];
                        Type t2 = arr2[idx];
                        generateParamTypeMap.putIfAbsent(t2.getTypeName(), t1);
                        log.info("{} = {}", t2.getTypeName(), t1.getTypeName());
                        //解析泛型的实际类型
                        models.addAll(appendPojo(getClassNameType(t1), t1));
                    }
                }

                //简称
                ShotName shotName = (ShotName) target.getAnnotation(ShotName.class);
                if (shotName != null) {
                    pojo.setPojoShortName(shotName.value());
                } else {
                    pojo.setPojoShortName(name);
                }

                //注释
                ClassDoc doc = getDoc(target.getName());
                if (null != doc) {
                    pojo.setDesc(doc.getModelCommentText());
                }

//                List<FieldModel> fieldModels = Arrays.stream(target.getFields())
//                        .map(item -> {
//                            FieldModel field = FieldModel.builder()
//                                    .name(item.getName())
//                                    .type(item.getType().getName())
//                                    .shortType(getShotName(item.getType().getName()))
//                                    .build();
//                            if (null != doc && null != doc.getFieldEntryMap()
//                                    && doc.getFieldEntryMap().containsKey(item.getName())) {
//                                field.setDesc(doc.getFieldEntryMap().get(item.getName()).getfExplain());
//                            }
//
//                            if (!isBaseType(field.getType())) {
//                                appendPojo(field.getType());
//                            }
//                            return field;
//                        })
//                        .collect(Collectors.toList());

                List<FieldModel> otherFieldModels = Arrays.stream(target.getMethods())
                        .filter(m -> (m.getName().startsWith("get") || m.getName().startsWith("is"))
                                && m.getParameterCount() <= 0)
                        .map(m -> {
                            String mName = m.getName();
                            String fName = null;
                            if (mName.startsWith("get")) {
                                mName = mName.substring(3);
                                fName = mName.substring(0, 1).toLowerCase() + mName.substring(1);
                            } else if (mName.startsWith("is")) {
                                fName = mName.substring(0, 1).toLowerCase() + mName.substring(1);
                            }

                            //识别是否废弃接口
                            Deprecated deprecated = (Deprecated) m.getAnnotation(Deprecated.class);

                            Type type = m.getGenericReturnType();
                            Class cls = m.getReturnType();
                            log.info("{}是否是泛型：{}", type.getTypeName(), type instanceof ParameterizedType);
                            if (type instanceof ParameterizedType) {
                                Type[] tt = ((ParameterizedType) type).getActualTypeArguments();
                                Type[] ptypes = new Type[tt.length];
                                int idx = 0;
                                for (Type t : tt) {
                                    if (generateParamTypeMap.containsKey(t.getTypeName())) {
                                        ptypes[idx++] = generateParamTypeMap.get(t.getTypeName());
                                    } else {
                                        ptypes[idx++] = t;
                                    }
                                }
                                ParameterizedType nType = TypeUtils.parameterize((Class<?>) ((ParameterizedType) type).getRawType(), ptypes);
                                log.info("新的类型：{}", nType.toString());
                                type = nType;
                                cls = (Class<?>) ((ParameterizedType) type).getRawType();
                            } else if (generateParamTypeMap.containsKey(type.getTypeName())) {
                                type = generateParamTypeMap.get(type.getTypeName());
                                if (type instanceof ParameterizedType) {
                                    cls = (Class<?>) ((ParameterizedType) type).getRawType();
                                } else {
                                    cls = (Class<?>) type;
                                }
                            }

                            FieldModel field = FieldModel.builder()
                                    .name(fName)
                                    .type(cls.getName())
                                    .genericType(type.getTypeName())
                                    .shortType(getShotType(type))
                                    .deprecated(deprecated != null)
                                    .build();
//                            if (generateParamTypeMap.containsKey(field.getGenericType())) {
//                                type = generateParamTypeMap.get(field.getGenericType());
//                                field.setType(getClassNameType(generateParamTypeMap.get(field.getGenericType())));
//                                field.setShortType(getShotType(generateParamTypeMap.get(field.getGenericType())));
//                                field.setGenericType(generateParamTypeMap.get(field.getGenericType()).getTypeName());
//                            }
                            if (null != doc && null != doc.getFieldEntryMap()
                                    && doc.getFieldEntryMap().containsKey(fName)) {
                                field.setDesc(doc.getFieldEntryMap().get(fName).getfExplain());
                            }

                            //处理返回对象
                            if (!isBaseType(field.getType()) && !excludeField.contains(field.getName())) {
                                models.addAll(appendPojo(field.getType(), type));
                            }
                            return field;
                        })
                        .filter(item -> !excludeField.contains(item.getName()))
                        .collect(Collectors.toList());
                pojo.setFieldEntryList(otherFieldModels);

                models.add(0, pojo);
                pojoMap.get(genericType.getTypeName()).addAll(models.stream().distinct().collect(Collectors.toList()));
                return models;
            } catch (ClassNotFoundException e) {
                log.error("解析Pojo失败", e);
            } catch (Throwable e) {
                log.error("解析Pojo失败", e);
            }
        }
        return Lists.newArrayList();
    }

    private boolean isBaseType(String type) {
        return ClassUtils.isBaseType(type);
    }

    private boolean isEnumType(String type) {
        Class cls = null;
        try {
            cls = loader.loadClass(type);
            boolean is = ClassUtils.isEnumTypeClass(cls);
            log.info("{}是否是枚举类：{}", type, is);
            return is;
        } catch (ClassNotFoundException e) {
            log.error("error", e);
        }
        return false;
    }

    private boolean isCollectionType(String type) {
        Class cls = null;
        try {
            cls = loader.loadClass(type);
            boolean is = ClassUtils.isArrayTypeClass(cls)
                    || ClassUtils.isListTypeClass(cls)
                    || ClassUtils.isMapTypeClass(cls)
                    || ClassUtils.isSetTypeClass(cls);
            log.info("{}是否是集合类：{}", type, is);
            return is;
        } catch (ClassNotFoundException e) {
            log.error("error", e);
        }
        return false;
    }
}
